package com.sp.business.normalization.impl;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.lang.NullArgumentException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sp.business.normalization.INormalizer;
import com.sp.business.normalization.NormalizationContext;
import com.sp.business.normalization.NormalizationListener;
import com.sp.business.normalization.RawFile;
import com.sp.business.objectmodel.NormalizedData;
import com.sp.generated.xml.firemon.AddressTranslationMethodProperty;
import com.sp.generated.xml.firemon.AddressTranslationMethods;
import com.sp.generated.xml.firemon.Applications;
import com.sp.generated.xml.firemon.BooleanProperty;
import com.sp.generated.xml.firemon.Column;
import com.sp.generated.xml.firemon.Database;
import com.sp.generated.xml.firemon.FireMon;
import com.sp.generated.xml.firemon.FirewallApplication;
import com.sp.generated.xml.firemon.InboundAppliedContext;
import com.sp.generated.xml.firemon.IntegerProperty;
import com.sp.generated.xml.firemon.Interface;
import com.sp.generated.xml.firemon.InterfaceReferenceCollection;
import com.sp.generated.xml.firemon.Member;
import com.sp.generated.xml.firemon.MemberCollection;
import com.sp.generated.xml.firemon.NatColumn;
import com.sp.generated.xml.firemon.Network;
import com.sp.generated.xml.firemon.NetworkObject;
import com.sp.generated.xml.firemon.NetworkObjectCollection;
import com.sp.generated.xml.firemon.NetworkObjectTypes;
import com.sp.generated.xml.firemon.NetworkReference;
import com.sp.generated.xml.firemon.NetworkReferenceCollection;
import com.sp.generated.xml.firemon.NormalizedDataSchema;
import com.sp.generated.xml.firemon.OutboundAppliedContext;
import com.sp.generated.xml.firemon.PolicyCollection;
import com.sp.generated.xml.firemon.RoutingInstance;
import com.sp.generated.xml.firemon.RoutingInstanceReferenceCollection;
import com.sp.generated.xml.firemon.ServiceObject;
import com.sp.generated.xml.firemon.ServiceObjectCollection;
import com.sp.generated.xml.firemon.ServiceObjectTypes;
import com.sp.generated.xml.firemon.StringProperty;
import com.sp.generated.xml.firemon.TranslatedColumn;
import com.sp.generated.xml.firemon.User;
import com.sp.generated.xml.firemon.UserCollection;
import com.sp.generated.xml.firemon.UserTypes;
import com.sp.generated.xml.firemon.ZoneReferenceCollection;

public abstract class BaseNormalizer implements INormalizer
{
  private static final Logger logger = LoggerFactory.getLogger(BaseNormalizer.class);

  //there are no name for 61,63,68,99, and 114, so they are skipped
  //in the future when need support for these protocols, add them here
  //To match Cisco change 0 to IP and 4 to IPINIP
  private static final String[] protocols
  = new String[]{
	    "IP", "0",
	    "ICMP", "1",
	    "IGMP", "2",
	    "GGP", "3",
	    "IPINIP", "4",
	    "ST", "5",
	    "TCP", "6",
	    "CBT", "7",
	    "EGP", "8",
	    "IGRP", "9",
	    "BBN-RCC-MON", "10",
	    "NVP-II", "11",
	    "PUP", "12",
	    "ARGUS", "13",
	    "EMCON", "14",
	    "XNET", "15",
	    "CHAOS", "16",
	    "UDP", "17",
	    "MUX", "18",
	    "DCN-MEAS", "19",
	    "HMP", "20",
	    "PRM", "21",
	    "XNS-IDP", "22",
	    "TRUNK-1", "23",
	    "TRUNK-2", "24",
	    "LEAF-1", "25",
	    "LEAF-2", "26",
	    "RDP", "27",
	    "IRTP", "28",
	    "ISO-TP4", "29",
	    "NETBLT", "30",
	    "MFE-NSP", "31",
	    "MERIT-INP", "32",
	    "DCCP", "33",
	    "3PC", "34",
	    "IDPR", "35",
	    "XTP", "36",
	    "DDP", "37",
	    "IDPR-CMTP", "38",
	    "TP++", "39",
	    "IL", "40",
	    "IPv6", "41",
	    "SDRP", "42",
	    "IPv6-Route", "43",
	    "IPv6-Frag", "44",
	    "IDRP", "45",
	    "RSVP", "46",
	    "GRE", "47",
	    "DSR", "48",
	    "BNA", "49",
	    "ESP", "50",
	    "AH", "51",
	    "I-NLSP", "52",
	    "SWIPE", "53",
	    "NARP", "54",
	    "MOBILE", "55",
	    "TLSP", "56",
	    "SKIP", "57",
	    "IPv6-ICMP", "58",
	    "IPv6-NoNxt", "59",
	    "IPv6-Opts", "60",
	    "CFTP", "62",
	    "SAT-EXPAK", "64",
	    "KRYPTOLAN", "65",
	    "RVD", "66",
	    "IPPC", "67",
	    "SAT-MON", "69",
	    "VISA", "70",
	    "IPCV", "71",
	    "CPNX", "72",
	    "CPHB", "73",
	    "WSN", "74",
	    "PVP", "75",
	    "BR-SAT-MON", "76",
	    "SUN-ND", "77",
	    "WB-MON", "78",
	    "WB-EXPAK", "79",
	    "ISO-IP", "80",
	    "VMTP", "81",
	    "SECURE-VMTP", "82",
	    "VINES", "83",
	    "TTP", "84",
	    "NSFNET-IGP", "85",
	    "DGP", "86",
	    "TCF", "87",
	    "EIGRP", "88",
	    "OSPF", "89",
	    "Sprite-RPC", "90",
	    "LARP", "91",
	    "MTP", "92",
	    "AX.25", "93",
	    "IPIP", "94",
	    "MICP", "95",
	    "SCC-SP", "96",
	    "ETHERIP", "97",
	    "ENCAP", "98",
	    "GMTP", "100",
	    "IFMP", "101",
	    "PNNI", "102",
	    "PIM", "103",
	    "ARIS", "104",
	    "SCPS", "105",
	    "QNX", "106",
	    "A/N", "107",
	    "PCP", "108",
	    "SNP", "109",
	    "Compaq-Peer", "110",
	    "IPX-in-IP", "111",
	    "VRRP", "112",
	    "PGM", "113",
	    "L2TP", "115",
	    "DDX", "116",
	    "IATP", "117",
	    "STP", "118",
	    "SRP", "119",
	    "UTI", "120",
	    "SMP", "121",
	    "SM", "122",
	    "PTP", "123",
	    "ISIS", "124",
	    "FIRE", "125",
	    "CRTP", "126",
	    "CRUDP", "127",
	    "SSCOPMCE", "128",
	    "IPLT", "129",
	    "SPS", "130",
	    "PIPE", "131",
	    "SCTP", "132",
	    "FC", "133",
	    "RSVP-E2E-IGNORE", "134",
	    "Mobility", "135",
	    "UDPLite", "136",
	    "MPLS-in-IP", "137",
	    "manet", "138",
	    "HIP", "139",
	    "Shim6", "140",
	    "ANY", "0"
	    };

  private static final List<String> protocolList = Arrays.asList(protocols);

  private static final String[] portLiterals = new String[] {
    "bgp", "179",
    "biff", "512",
    "bootpc", "68",
    "bootps", "67",
    "chargen", "19",
    "citrix-ica", "1494",
    "cmd", "514",
    "daytime", "13",
    "discard", "9",
    "domain", "53",
    "dnsix", "195",
    "echo", "7",
    "exec", "512",
    "finger", "79",
    "ftp", "21",
    "ftp-data", "20",
    "gopher", "70",
    "hostname", "101",
    "http", "80",
    "https", "443",
    "nameserver", "42",
    "ident", "113",
    "irc", "194",
    "isakmp", "500",
    "klogin", "543",
    "kshell", "544",
    "lpd", "515",
    "login", "513",
    "mobile-ip", "434",
    "netbios-ns", "137",
    "netbios-dgm", "138",
    "nntp", "119",
    "ntp", "123",
    "pim-auto-rp", "496",
    "pop2", "109",
    "pop3", "110",
    "radius", "1645-1646",
    "rip", "520",
    "smtp", "25",
    "snmp", "161",
    "snmptrap", "162",
    "sqlnet", "1521",
    "ssh", "22",
    "sunrpc", "111",
    "syslog", "514",
    "tacacs", "49",
    "talk", "517",
    "telnet", "23",
    "tftp", "69",
    "time", "37",
    "uucp", "540",
    "who", "513",
    "whois", "43",
    "www", "80",
    "xdmcp", "177"
    };

  public enum RoutingInstanceTypes
  {
	  Forwarding("Forwarding"),
	  Layer2VPN("Layer2VPN"),
	  Layer2Control("Layer2Control"),
	  NoForwarding("NoForwarding"),
	  VirtualRouter("virtual_router"),
	  VirtualSwitch("VirtualSwitch"),
	  VPLS("VPLS"),
	  VRF("VRF");

	 public final String type;

	 RoutingInstanceTypes(String type)
	 {
		  this.type = type;
	 }
  }

  private static final List<String> portLiteralsList = Arrays.asList(portLiterals);

  protected Map<String, Object> serviceObjectMap = new HashMap<String, Object>();
  protected Map<String, Object> networkObjectMap = new HashMap<String, Object>();
  protected Map<String, Object> interfaceMap = new HashMap<String, Object>();
  protected Map<String, Object> zoneMap = new HashMap<String, Object>();
  protected Map<String, Object> policyApplicationMap = new HashMap<String, Object>();
  protected Map<String, Object> userMap = new HashMap<String, Object>();
  protected Map<String, RoutingInstance> routingInstanceMap = new HashMap<String, RoutingInstance>();

  protected NormalizationListener listener = null;

  public void setListener(NormalizationListener listener)
  {
    this.listener = listener;
  }

  public boolean supportsProductVersion(UUID productVersionKey)
  {
    return false;
  }

  public Set<UUID> getCapabilities()
  {
    return new HashSet<UUID>();
  }

  public void publishNotificationCompleted(NormalizedDataSchema schema)
  {
    if (listener != null)
    {
      listener.normalizationCompleted(schema);
    }
  }

  protected void publishStatusRemark(String remark)
  {
    if (listener != null)
    {
      listener.updateStatusRemark(remark);
    }
  }

  public abstract NormalizedDataSchema normalize(
      NormalizationContext normalizationContext,
      NormalizedDataSchema previous,
      List<RawFile> rawFiles) throws Exception;

  protected NormalizedDataSchema createEmptyNormalizedDataSchema()
  {
    NormalizedDataSchema normalizedDataSchema = new NormalizedDataSchema();
    FireMon firemon = new FireMon();
    Applications applications = new Applications();
    FirewallApplication firewallApplication = new FirewallApplication();
    Database database = new Database();
    ServiceObjectCollection serviceObjects = new ServiceObjectCollection();
    NetworkObjectCollection networkObjects = new NetworkObjectCollection();
    UserCollection users = new UserCollection();
    PolicyCollection policies = new PolicyCollection();

    normalizedDataSchema.setFireMon(firemon);
    firemon.setApplications(applications);
    applications.setFirewallApplication(firewallApplication);
    firewallApplication.setDatabase(database);

    database.setNetworkObjects(networkObjects);
    database.setPolicies(policies);
    database.setServiceObjects(serviceObjects);
    database.setUsers(users);

    return normalizedDataSchema;
  }

  protected Database getOrCreateDatabase(NormalizedDataSchema normalizedDataSchema)
  {
    FireMon fireMon = normalizedDataSchema.getFireMon();
    if (fireMon == null)
    {
      fireMon = new FireMon();
      normalizedDataSchema.setFireMon(fireMon);
    }

    Applications applications = fireMon.getApplications();
    if (applications == null)
    {
      applications = new Applications();
      fireMon.setApplications(applications);
    }

    FirewallApplication firewallApplication = applications.getFirewallApplication();
    if (firewallApplication == null)
    {
      firewallApplication = new FirewallApplication();
      applications.setFirewallApplication(firewallApplication);
    }

    Database database = firewallApplication.getDatabase();
    if (database == null)
    {
      database = new Database();
      firewallApplication.setDatabase(database);
    }

    return database;
  }

  public static RawFile getRawFileByName(
      List<RawFile> rawFiles,
      String name) throws Exception
  {
    if (rawFiles == null || rawFiles.size() == 0)
    {
      throw new Exception("no raw config files exist for: " + name);
    }

    for (RawFile rawFile : rawFiles)
    {
      if (name.equalsIgnoreCase(rawFile.getName()))
      {
        return rawFile;
      }
    }
    return null;
  }

  public static List<String> getRawFileLines(List<RawFile> rawFiles, String name) throws Exception
  {
    for (RawFile rawFile : rawFiles)
    {
      if (name.equalsIgnoreCase(rawFile.getName()))
      {
        List<String> lines = new ArrayList<String>();
        BufferedReader br = null;
        try
        {
          br = new BufferedReader(new InputStreamReader(rawFile.getInputStream()));
          String line = br.readLine();
          while (line != null)
          {
            lines.add(line);
            line = br.readLine();
          }
        }
        finally
        {
          br.close();
        }
        return lines;
      }
    }
    return null;
  }

  public static boolean isInteger(String testString)
  {
	if( testString == null )
  {
    return false;
  }

    for (int index = 0; index < testString.length(); index++)
    {
      if (testString.charAt(index) < '0' || testString.charAt(index) > '9')
      {
        return false;
      }
    }
    return true;
  }

  public static String getPlainProtoName(Integer protoNum)
  {
	  String protocol ="Any";
	  int index = protocolList.indexOf(protoNum+"");
	  if(index != -1)
	  {
		  protocol = protocolList.get(index -1);
	  }
	  return protocol;
  }

  //if it is digital return
  //else try to get from protocol list
  public static Integer getProtocolNumber(String protoName)
  {
    Integer protoNumber = -1;
    if(protoName==null)
    {
      return protoNumber;
    }

    if (isInteger(protoName))
    {
      protoNumber = Integer.parseInt(protoName);
    }
    else
    {
      int index = protocolList.indexOf(protoName.toUpperCase());
      if (index != -1)
      {
        String num = protocolList.get(index + 1);
        protoNumber = Integer.parseInt(num);
      }
    }
    return protoNumber;
  }

  public static String getPorts(String portLiteral)
  {
    if (isInteger(portLiteral))
    {
      return portLiteral;
    }

    int index = portLiteralsList.indexOf(portLiteral);
    if (index == -1)
    {
      return portLiteral;
    }

    return portLiteralsList.get(index + 1);
  }

  public static String createXsid(String elementName, String objectName)
  {
    String safeObjectName = createSafeObjectName(objectName);
    return elementName + safeObjectName;
  }

  //considered to create Xsid for network, but group member may not have type info
  //such as fortinet group address, if add type info in xsid during network object creation
  //it will need to search all possible type to find a member
  public static String createXsid(String elementName, String zone, String objectName)
  {
    String safeObjectName = createSafeObjectName(objectName);
    return elementName + zone + safeObjectName;
  }

  public static String createXsid(String elementName, String zone, String objectName, String translatedIP)
  {
    String safeObjectName = createSafeObjectName(objectName);
    return elementName + zone + safeObjectName + translatedIP;
  }

  public static String createSafeObjectName(String objectName)
  {
    String safeObjectName = "";

    String safeCharacters = "1234567890-qwertyuiopasdfghjklzxcvbnm._QWERTYUIOPASDFGHJKLZXCVBNM";

    for (char c : objectName.toCharArray())
    {
      if (safeCharacters.contains(Character.toString(c)))
      {
        safeObjectName += c;
      }
      else
      {
        logger.debug(String.format("stripped unsafe xs:ID character %s from %s", c, objectName));
      }
    }
    return safeObjectName;
  }

  protected ServiceObject createServiceGroupObject(Database database, String name)
  {
    String xsid = createServiceXsid("ServiceObject", ServiceObjectTypes.SERVICE_GROUP.value(), name);
    ServiceObject obj = (ServiceObject) serviceObjectMap.get(xsid);
    if (obj == null)
    {
      ServiceObject serviceObject = new ServiceObject();
      StringProperty sp = NormalizedData.createStringProperty(name);
      serviceObject.setName(sp);
      xsid = createServiceXsid("ServiceObject", ServiceObjectTypes.SERVICE_GROUP.value(), name);
      serviceObject.setXsid(xsid);
      serviceObject.setType(ServiceObjectTypes.SERVICE_GROUP);
      database.getServiceObjects().getServiceObjects().add(serviceObject);
      serviceObjectMap.put(xsid, serviceObject);
      serviceObjectMap.put(name, serviceObject);
      obj = serviceObject;
    }
    return obj;
  }

  protected ServiceObject createServiceGroupObject(Database database, String name, String comment)
  {
     ServiceObject obj = createServiceGroupObject(database, name);
     if(StringUtils.isNotBlank(comment))
    {
      obj.setComments(NormalizedData.createStringProperty(comment));
    }
     return obj;
  }

  protected void addMemberToServiceObjectGroup(ServiceObject group, ServiceObject serviceObjMember)
  {
    if (serviceObjMember == null)
    {
      return;
    }

    //add this member to serviceGroup
    if (group != null)
    {
      //prevent recursion
      if (group == serviceObjMember)
      {
        logger.debug("do not add to itself " + group.getName().getValue());
        return;
      }
      Member m = new Member();
      m.setObject(serviceObjMember);
      if (group.getMembers() == null)
      {
        MemberCollection mc = new MemberCollection();
        mc.getMembers().add(m);
        group.setMembers(mc);
      }
      else
      {
        //member is not present
        boolean memberExists = false;
        List<Member> mList = group.getMembers().getMembers();
        for (Member m1 : mList)
        {
          ServiceObject so = (ServiceObject) m1.getObject();
          if (so == serviceObjMember)
          {
            memberExists = true;
            break;
          }
        }
        if (memberExists == false)
        {
          group.getMembers().getMembers().add(m);
        }
      }
    }
  }

  protected ServiceObject createServiceObjectByType(
      Database database,
      String name,
      String portStart,
      String portEnd,
      String protocol,
      String description)
  {
     if(protocol==null)
    {
      protocol = "Any";
    }

    //only type need change is ip - in Cisco ip any mean Any, tcp any means TCP-Any
    //for UI to display correctly
    //if (name.equalsIgnoreCase("any") && serviceType.equalsIgnoreCase("ip"))
    if ((name.equalsIgnoreCase("any") || name.equalsIgnoreCase("ip"))
        && !(protocol.equalsIgnoreCase("tcp") || protocol.equalsIgnoreCase("udp") || protocol.equalsIgnoreCase("icmp")))
    {
      protocol = "Any";
    }

    String xsid = createServiceXsid("ServiceObject", protocol, name);
    ServiceObject obj = (ServiceObject) serviceObjectMap.get(xsid);
    if (obj == null)
    {
      obj = new ServiceObject();
      StringProperty sp = NormalizedData.createStringProperty(name);
      obj.setName(sp);
      if (StringUtils.isNotBlank(portStart))
      {
        IntegerProperty port = new IntegerProperty();
        try
        {
          port.setValue(Integer.parseInt(portStart));
        }
        catch (NumberFormatException ex)
        {
          //pre-defined service name are prefixed with protocol
          String pStartName = portStart;
          if (portStart != null && !portStart.equalsIgnoreCase("Any"))
          {
            pStartName = protocol + "-" + portStart;
          }
          ServiceObject serviceObj = ((ServiceObject) serviceObjectMap.get(pStartName));
          if(serviceObj !=null)
          {
            if (serviceObj.getPort() != null)
            {
              Integer portNumber = serviceObj.getPort().getValue();
              port.setValue(portNumber);
            }
            else
            {
              logger.info(String.format( "service %s  has no port defined", name));
            }
          }
          else
          {
            Integer intPort = 0;
            if( !isInteger(portStart))
            {
              String nPort = getPorts(portStart);
              if(isInteger(nPort))
              {
                intPort = Integer.parseInt(nPort);
              }
            }
            logger.info("port object not found in serviceObjectMap " + portStart);
            port.setValue(intPort);
          }
        }
        obj.setPort(port);
      }
      if (StringUtils.isNotBlank(portEnd))
      {
        IntegerProperty pe = new IntegerProperty();
        try
        {
          pe.setValue(Integer.parseInt(portEnd));
        }
        catch (NumberFormatException ex)
        {
          //not a integer such as netbios-ssn
          String pEndName = portStart;
          if (portEnd != null && !portEnd.equalsIgnoreCase("Any"))
          {
            pEndName = protocol + "-" + portEnd;
          }
          ServiceObject serviceObj = ((ServiceObject) serviceObjectMap.get(pEndName));

          if (serviceObj != null)
          {
            //Integer portEndNumber = serviceObj.getPort().getValue();
            Integer portEndNumber = 0;
            if(serviceObj.getPort()!=null)
            {
              portEndNumber = serviceObj.getPort().getValue();
            }

            pe.setValue(portEndNumber);
          }
          else
          {
            Integer intPort = 0;
            if( !isInteger(portEnd))
            {
              String nPort = getPorts(portEnd);
              if(isInteger(nPort))
              {
                intPort = Integer.parseInt(nPort);
              }
            }
            logger.info("port end object not found in serviceObjectMap " + portEnd);
            pe.setValue(intPort);
          }
        }
        obj.setPortRangeEnd(pe);
        BooleanProperty bp = new BooleanProperty();
        bp.setValue(true);
        obj.setPortRange(bp);
      }

      obj.setXsid(xsid);

      // set comments
      obj.setComments(NormalizedData.createStringProperty(description));

      //add protocol
      Integer proto = getProtocolNumber(protocol);
      if (proto != -1)
      {
        IntegerProperty ip = new IntegerProperty();
        ip.setValue(proto);
        obj.setProtocol(ip);
      }

      //special case for icmp, most likely it is a member of
      if (proto == 1 && name.equals("icmp"))
      {
        //set icmp_type = -1
        IntegerProperty p = new IntegerProperty();
        p.setValue(-1);
        obj.setIcmpType(p);
      }

      //AnyPort TCP-Any, UDP-Any, ICMP-Any
      //policy check will check protocol and AnyPort first
      //if name ends with -Any or name equals protocol name
      if (name.endsWith("-Any") || name.equalsIgnoreCase(protocol) || protocol.equalsIgnoreCase("Any") )
      {
        BooleanProperty bAnyPort = new BooleanProperty();
        bAnyPort.setValue(true);
        obj.setAnyPort(bAnyPort);
      }

      //if protocol is a number, get plain name
      //NSM predefined service
      //Name:    AOL
      //Category:    remote    ID:    0    Flag: Pre-defined
      //Transport    Src port     Dst port   ICMPtype,code  Timeout(min) Application
      //6            1/65535      5190/5194
      if(isInteger(protocol)==true)
      {
      	int protoNum = Integer.parseInt(protocol);
      	protocol = getPlainProtoName(protoNum).toLowerCase();
      }
      //set type
      obj.setType(resolveServiceObjectType(protocol));

      database.getServiceObjects().getServiceObjects().add(obj);
      serviceObjectMap.put(xsid, obj);
      serviceObjectMap.put(name, obj);
    }
    return obj;
  }

  //for icmp type only, for tcp and udp object use above method
  //input portStart, portEnd could be empty or null
  protected ServiceObject createICMPServiceObjectByType(
      Database database,
      String name,
      String protocol,
      String description,
      Integer icmpType)
  {
    ServiceObject obj = createServiceObjectByType(database, name, null, null, protocol, description);

    //set icmp type
    IntegerProperty p = new IntegerProperty();
    Integer icmpTypeValue = icmpType;
    if (icmpType == null)
    {
      icmpTypeValue = -1;
    }
    p.setValue(icmpTypeValue);
    obj.setIcmpType(p);

    //junos-icmp-all-t1
    //ICPM-ALL (Secui)
    if (name.endsWith("-Any") || name.equalsIgnoreCase(protocol) || name.contains("icmp-all") || name.equalsIgnoreCase("icmp-all"))
    {
      BooleanProperty bAnyPort = new BooleanProperty();
      bAnyPort.setValue(true);
      obj.setAnyPort(bAnyPort);
    }

    return obj;
  }



  protected ServiceObjectTypes resolveServiceObjectType(String serviceType)
  {
    ServiceObjectTypes t = null;
    if (serviceType != null)
    {
      if (serviceType.equalsIgnoreCase("Any"))
      {
        t = ServiceObjectTypes.ANY;
      }
      else if (serviceType.equalsIgnoreCase("tcp") || serviceType.equals("TcpService"))
      {
        t = ServiceObjectTypes.TCP_SERVICE;
      }
      else if (serviceType.equalsIgnoreCase("udp") || serviceType.equals("UdpService"))
      {
        t = ServiceObjectTypes.UDP_SERVICE;
      }
      else if (serviceType.equalsIgnoreCase("icmp") || serviceType.equals("IcmpService"))
      {
        t = ServiceObjectTypes.ICMP_SERVICE;
      }
      else if (serviceType.equalsIgnoreCase("ServiceGroup"))
      {
        t = ServiceObjectTypes.SERVICE_GROUP;
      }
      else
      {
        t = ServiceObjectTypes.OTHER_SERVICE;
      }
    }
    return t;

  }

  //for service object
  protected String createServiceXsid(String elementName, String serviceType, String objectName)
  {
    ServiceObjectTypes t = resolveServiceObjectType(serviceType);
    String type = (t == null) ? "" : t.value();
    String safeObjectName = createSafeObjectName(objectName);
    return elementName + type + safeObjectName;
  }

  protected String translateProtocol(String proto)
  {
    if ("0".equals(proto))
    {
      return "Any";
    }
    else if ("1".equals(proto) || "icmp".equalsIgnoreCase(proto))
    {
      return "IcmpService";
    }
    else if ("6".equals(proto) || "tcp".equalsIgnoreCase(proto))
    {
      return "TcpService";
    }
    else if ("17".equals(proto) || "udp".equalsIgnoreCase(proto))
    {
      return "UdpService";
    }
    else if ("50".equals(proto))
    {
      return "ESP";
    }
    else if ("89".equals(proto))
    {
      return "OSPFIGP";
    }
    return "OtherService";
  }

  protected NetworkObject createNetworkObject(
      Database database,
      String name,
      String ip,
      String mask,
      NetworkObjectTypes type,
      String description)
  {
    String xsid = createXsid("NetworkObject", name);
    return createNetworkObjectImpl(database, name, ip, mask, type, description, xsid);
  }

  protected NetworkObject createNetworkObject(
      Database database,
      String name,
      String ip,
      String mask,
      NetworkObjectTypes type,
      String zone,
      String description)
  {
    String xsid = createXsid("NetworkObject", zone, name);
    NetworkObject obj = createNetworkObjectImpl(database, name, ip, mask, type, description, xsid);
    if(!StringUtils.isBlank(zone))
    {
    	StringProperty sp = new StringProperty();
    	sp.setValue(zone);
    	obj.setZone(sp);
    }
    return obj;
  }

  //default xsid is not suitable for network object searching
  //see SecuiNormalizer, network object has Host and Network type
  //and the name could be the same in the same zone
  //xsid should not be blank
  //could return null
  protected NetworkObject createNetworkObject(
	      Database database,
	      String name,
	      String ip,
	      String mask,
	      NetworkObjectTypes type,
	      String zone,
	      String description,
	      String xsid)
	  {
	    if(StringUtils.isBlank(xsid))
	    {
	    	logger.error("xisd should not be null for network object " + "name: " + name + " ip:" + ip + " mask: "+ mask + " zone:" + zone);
	    	return null;
	    }
	    NetworkObject obj = createNetworkObjectImpl(database, name, ip, mask, type, description, xsid);
	    if(!StringUtils.isBlank(zone))
	    {
	    	StringProperty sp = new StringProperty();
	    	sp.setValue(zone);
	    	obj.setZone(sp);
	    }
	    return obj;
	  }

  //for vip nat rule, same ip could be mapped to different ips
  //use xsid to distinguish them
  protected NetworkObject createNetworkObject(
	      Database database,
	      String name,
	      String ip,
	      String mask,
	      String translatedAddress,
	      AddressTranslationMethodProperty trMethodProperty,
	      NetworkObjectTypes type,
	      String zone,
	      String description)
	  {
	    String xsid = createXsid("NetworkObject", zone, name, translatedAddress);
	    NetworkObject obj = createNetworkObjectImpl(database, name, ip, mask, type, description, xsid);
	    if(!StringUtils.isBlank(zone))
	    {
	    	StringProperty sp = new StringProperty();
	    	sp.setValue(zone);
	    	obj.setZone(sp);
	    }
	    if(!StringUtils.isBlank(translatedAddress))
	    {
      obj.setTranslatedAddress(NormalizedData.createStringProperty(translatedAddress));
	    	obj.setTranslationMethod(trMethodProperty);
	    }
            logger.debug(obj.toString());
	    return obj;
	  }

  //SRX allow this: 10.20.3.0/24 to 10.20.4.0/16
  //startIP 10.20.3.0/24
  protected NetworkObject createRangeNetworkObject(
      Database database,
      String name,
      String startIP,
      String endIP,
      String zone,
      String description)
  {
    String zoneStr = zone;
    if (StringUtils.isBlank(zone))
    {
      zoneStr = "";
    }
    String xsid = createXsid("NetworkObject", zoneStr, name);
    return createRangeNetworkObjectImpl(database, name, startIP, endIP, zone, description, xsid);
  }


  //xsid should not be null
  //could return null
  protected NetworkObject createRangeNetworkObject(
	      Database database,
	      String name,
	      String startIP,
	      String endIP,
	      String zone,
	      String description,
	      String xsid)
	  {

	    if(StringUtils.isBlank(xsid))
	    {
	    	logger.error("xisd should not be null for range network object " + "name: " + name + " startIP:" + startIP + " endIP: "+ endIP + " zone:" + zone);
	    	return null;
	    }
	    return createRangeNetworkObjectImpl(database, name, startIP, endIP, zone, description, xsid);
	  }

  protected NetworkObject createRangeNetworkObjectImpl(
	      Database database,
	      String name,
	      String startIP,
	      String endIP,
	      String zone,
	      String description,
	      String xsid)
	  {
    NetworkObject obj = (NetworkObject) networkObjectMap.get(xsid);
    if (obj == null)
    {
      NetworkObject networkObject = new NetworkObject();
      StringProperty sp = NormalizedData.createStringProperty(name);
      networkObject.setName(sp);

      StringProperty addrStart = NormalizedData.createStringProperty(startIP);
      networkObject.setIPAddressStart(addrStart);
      StringProperty addrEnd = NormalizedData.createStringProperty(endIP);
      networkObject.setIPAddressEnd(addrEnd);
      networkObject.setType(NetworkObjectTypes.ADDRESS_RANGE);

      if (description != null && !description.isEmpty())
      {
        StringProperty comment = NormalizedData.createStringProperty(description);
        networkObject.setComments(comment);
      }
		    if (!StringUtils.isBlank(zone))
		    {
        networkObject.setZone(NormalizedData.createStringProperty(zone));
		    }
      networkObject.setXsid(xsid);
      database.getNetworkObjects().getNetworkObjects().add(networkObject);
      networkObjectMap.put(xsid, networkObject);
      obj = networkObject;
    }
    return obj;
  }

  private NetworkObject createNetworkObjectImpl(
      Database database,
      String name,
      String ip,
      String mask,
      NetworkObjectTypes type,
      String description,
      String xsid)
  {
    NetworkObject obj = (NetworkObject) networkObjectMap.get(xsid);
    if (obj == null)
    {
      NetworkObject networkObject = new NetworkObject();
      StringProperty sp = NormalizedData.createStringProperty(name);
      networkObject.setName(sp);

      StringProperty ipAddress = NormalizedData.createStringProperty(ip);
      networkObject.setIPAddress(ipAddress);
      StringProperty netmask = NormalizedData.createStringProperty(mask);
      networkObject.setNetmask(netmask);
      networkObject.setType(type);

      if (description != null && !description.isEmpty())
      {
        StringProperty comment = NormalizedData.createStringProperty(description);
        networkObject.setComments(comment);
      }
      networkObject.setXsid(xsid);
      database.getNetworkObjects().getNetworkObjects().add(networkObject);
      networkObjectMap.put(xsid, networkObject);
      obj = networkObject;
    }
    return obj;
  }

  //unify calling interface
  protected String createGroupNetworkObjectXsid(String name)
  {
	  return createXsid("NetworkObjectGroup", name);
  }

  protected String createGroupNetworkObjectXsid(String zone, String name)
  {
	  return createXsid("NetworkObjectGroup", zone, name);
  }

  //for device like Cisco
  protected NetworkObject createNetworkGroupObject(Database database, String name)
  {
	String xsid = createGroupNetworkObjectXsid(name);
    return createNetworkGroupObjectImp(database, name, xsid);
  }

  protected NetworkObject createNetworkGroupObject(Database database, String zone, String name)
  {
	String xsid = createGroupNetworkObjectXsid(zone, name);
    return createNetworkGroupObjectImp(database, zone, name, xsid);
  }

  protected NetworkObject createNetworkGroupObject(Database database, String zone, String name, String desc)
  {
	String xsid = createGroupNetworkObjectXsid(zone, name);
    NetworkObject obj = createNetworkGroupObjectImp(database, zone, name, xsid);
    if(!StringUtils.isBlank(desc))
    {
      obj.setComments(NormalizedData.createStringProperty(desc));
    }
    return obj;
  }

  private NetworkObject createNetworkGroupObjectImp(Database database, String zone, String name, String xsid)
  {
    NetworkObject obj = createNetworkGroupObjectImp(database, name, xsid);
    if(!StringUtils.isBlank(zone))
    {
      obj.setZone(NormalizedData.createStringProperty(zone));
    }
    return obj;
  }

  private NetworkObject createNetworkGroupObjectImp(Database database, String name, String xsid)
  {
    NetworkObject obj = null;
    if (networkObjectMap.get(xsid) == null)
    {
      NetworkObject networkObject = new NetworkObject();
      StringProperty sp = NormalizedData.createStringProperty(name);
      networkObject.setName(sp);
      networkObject.setXsid(xsid);
      networkObject.setType(NetworkObjectTypes.NETWORK_OBJECT_GROUP);

      database.getNetworkObjects().getNetworkObjects().add(networkObject);
      networkObjectMap.put(xsid, networkObject);
      obj = networkObject;
    }
    else
    {
      return (NetworkObject) networkObjectMap.get(xsid);
    }
    return obj;
  }

  protected void addMemberToNetworkObjectGroup(NetworkObject groupObj, Object networkObjMember)
  {
    if (networkObjMember == null)
    {
      return;
    }

    //add this member to serviceGroup
    if (groupObj != null)
    {
      //prevent recursion
      if (groupObj  == networkObjMember)
      {
         logger.debug("do not add to itself " + groupObj.getName().getValue());
         return;
      }

      Member m = new Member();
      m.setObject(networkObjMember);
      if (groupObj.getMembers() == null)
      {
        MemberCollection mc = new MemberCollection();
        groupObj.setMembers(mc);
      }

      groupObj.getMembers().getMembers().add(m);
    }
  }

  protected User createUser(Database database, String userName)
  {
    User user = new User();
    user.setUID("");
    user.setName(NormalizedData.createStringProperty(userName));
    user.setComments(NormalizedData.createStringProperty(""));
    user.setType(UserTypes.USER);
    database.getUsers().getUsers().add(user);

    this.userMap.put(userName, user);
    this.userMap.put("User" + userName, user);
    return user;
  }

  protected User createUserGroup(Database database, String groupName)
  {
    User user = new User();
    user.setUID("");

    user.setName(NormalizedData.createStringProperty(groupName));

    user.setComments(NormalizedData.createStringProperty(""));

    user.setType(UserTypes.USER_GROUP);
    database.getUsers().getUsers().add(user);

    this.userMap.put(groupName, user);
    this.userMap.put("User" + groupName, user);
    return user;
  }

  protected void addMemberToUserGroup(User groupObj, Object userMember)
  {
    if (groupObj == null)
    {
      return;
    }

    //add this member to serviceGroup
    if (groupObj != null)
    {
      Member m = new Member();
      m.setObject(userMember);
      if (groupObj.getMembers() == null)
      {
        MemberCollection mc = new MemberCollection();
        groupObj.setMembers(mc);
      }

      groupObj.getMembers().getMembers().add(m);
    }
  }

  protected Interface createInterface(final String name, final String ip, final String mask)
  {
    final Interface intf = new Interface();

    intf.setNetworks(new NetworkReferenceCollection());

    intf.setName(NormalizedData.createStringProperty(name));
    final String xsid = createXsid("Interface", name);
    intf.setXsid(xsid);
    intf.setUID(xsid);

    final StringProperty ipProperty = NormalizedData.createStringProperty(StringUtils.trimToEmpty(ip));
    intf.setIPAddress(ipProperty);

    final StringProperty netmaskProperty = NormalizedData.createStringProperty(StringUtils.trimToEmpty(mask));
    intf.setNetmask(netmaskProperty);

    this.interfaceMap.put(intf.getXsid(), intf);
    this.interfaceMap.put(name, intf);

    return intf;
  }

  protected Column createColumn(final Object... objects)
  {
    final MemberCollection members = new MemberCollection();

    for(final Object ith : objects)
    {
      final Member member = new Member();
      member.setObject(ith);
      members.getMembers().add(member);
    }

    final Column column = new Column();
    column.setNegated(NormalizedData.createBooleanProperty(false));
    column.setMembers(members);

    return column;
  }

  protected NatColumn createNatColumn(final Object... objects)
  {
    final MemberCollection members = new MemberCollection();

    for(final Object ith : objects)
    {
      final Member member = new Member();
      member.setObject(ith);
      members.getMembers().add(member);
    }

    final NatColumn column = new NatColumn();
    column.setMembers(members);

    return column;
  }

  protected TranslatedColumn createTranslatedColumn(final Object... objects)
  {
    final MemberCollection members = new MemberCollection();

    for(final Object ith : objects)
    {
      final Member member = new Member();
      member.setObject(ith);
      members.getMembers().add(member);
    }

    final TranslatedColumn column = new TranslatedColumn();
    column.setTranslationMethod(new AddressTranslationMethodProperty());
    column.getTranslationMethod().setValue(AddressTranslationMethods.NONE);
    column.setMembers(members);

    return column;
  }

  protected InboundAppliedContext createNatInboundAppliedContext(
      final Boolean isAny,
      final InterfaceReferenceCollection interfaceReferences,
      final ZoneReferenceCollection zoneReferences,
      final RoutingInstanceReferenceCollection routingInstanceReferences)
  {
    final InboundAppliedContext from = new InboundAppliedContext();
    from.setInterfaces(interfaceReferences);
    from.setZones(zoneReferences);
    from.setRoutingInstances(routingInstanceReferences);
    from.setIsAny(NormalizedData.createBooleanProperty(isAny == null ? false : isAny));

    return from;
  }

  protected OutboundAppliedContext createNatOutboundAppliedContext(
      final Boolean isAny,
      final InterfaceReferenceCollection interfaceReferences,
      final ZoneReferenceCollection zoneReferences,
      final RoutingInstanceReferenceCollection routingInstanceReferences)
  {
    final OutboundAppliedContext from = new OutboundAppliedContext();
    from.setInterfaces(interfaceReferences);
    from.setZones(zoneReferences);
    from.setRoutingInstances(routingInstanceReferences);
    from.setIsAny(NormalizedData.createBooleanProperty(isAny == null ? false : isAny));

    return from;
  }

  //for route normalization
  public static boolean containsNetwork(NetworkReferenceCollection networkReferences, String address)
  {
	    if (networkReferences == null)
	    {
	      throw new NullArgumentException("networkReferences");
	    }

	    for (NetworkReference networkReference : networkReferences.getNetworkReferences())
	    {
	      Object obj = networkReference.getNetwork();

	      if (!(obj instanceof Network))
	      {
	        continue;
	      }

	      Network network = (Network) obj;
	      if (network.getAddress() == null)
	      {
	        continue;
	      }

	      if (StringUtils.equals(network.getAddress().getValue(), address))
	      {
	        return true;
	      }
	    }

	    return false;
	  }


}
